/*
 * Copyright (C) 2009 Christoph Caks
 *
 * This file is part of GLWAK.
 *
 * GLWAK is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * GLWAK is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with GLWAK.  If not, see <http://www.gnu.org/licenses/>.
 *
 *
 * X11Context.cpp
 *
 *  Created on: 08.07.2009
 *      Author: Christoph Caks
 */

#include "x11/X11Context.h"

// from GL3/gl3.h
#ifndef GL_SHADING_LANGUAGE_VERSION
#define GL_SHADING_LANGUAGE_VERSION       0x8B8C
#endif

#ifndef GLX_CONTEXT_MAJOR_VERSION_ARB
#define GLX_CONTEXT_MAJOR_VERSION_ARB       0x2091
#define GLX_CONTEXT_MINOR_VERSION_ARB       0x2092
typedef GLXContext (*glXCreateContextAttribsARBProc)(Display*, GLXFBConfig,
        GLXContext, Bool, const int*);
#define GLX_CONTEXT_FLAGS_ARB 				0x2094
#define GLX_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB 0x00000002
#endif

namespace glwak {

namespace priv {

X11Context::X11Context() {
	// use a dummy window

	if (this->window == NULL) {
		WindowConfiguration wc("Context Dummy Window", 30, 30, false);
		this->window = new X11Window();
		this->window->setConfiguration(wc);
		this->window->initialize();
	}

	// XXX i dont think this will work, but i put it here anyway
	this->display = this->window->getDisplay();

}

X11Context::X11Context( X11Context* _shareContext ) :
	Context( _shareContext ) {

	this->configuration = _shareContext->configuration;

	this->window = 0;
	this->display = XOpenDisplay( 0 );

}

X11Context::X11Context(Window *_window) :
	Context(_window) {
	this->window = dynamic_cast<X11Window*> (_window);
	this->display = this->window->getDisplay();
}

X11Context::~X11Context() {
	// TODO Auto-generated destructor stub
}

void *X11Context::getExtensionAddress(const char *_extension) {
	//void (*tmp)() = glXGetProcAddress( (const GLubyte*) _extension );
	//return &tmp;
}
#if 0
XWindowAttributes X11Context::getXWindowAttributes() {
	XWindowAttributes windowAttributes;
	if ( XGetWindowAttributes( this->window->getDisplay(), this->window->getWindow(), &windowAttributes ) == 0 ) {
		throw "Failed to get the window attributes";
	}
	return windowAttributes;
}

XVisualInfo X11Context::createVisualInfoTemplate( XWindowAttributes windowAttributes ) {
	XVisualInfo templ;
	templ.depth = windowAttributes.depth; // < FIXTHIS
	templ.visualid = XVisualIDFromVisual(windowAttributes.visual);
	templ.screen = DefaultScreen(window->getDisplay());
	return templ;
}

XVisualInfo *X11Context::getPossibleVisuals( int &nbVisuals, XVisualInfo& templ ) {
	XVisualInfo* visuals = XGetVisualInfo( window->getDisplay(), VisualDepthMask | VisualIDMask | VisualScreenMask, &templ, &nbVisuals );
	if (!visuals || (nbVisuals == 0)) {
		if (visuals) {
			XFree(visuals);
		}
		throw "There is no valid visual for the selected screen";
	}
	return visuals;
}

XVisualInfo* X11Context::findBestVisual( int nbVisuals, XVisualInfo* visuals ) {
	// TODO go through this


	int bitsPerPixel = 24;
	// Find the best visual
	int bestScore = 0xFFFF;
	XVisualInfo* bestVisual = NULL;
	while (!bestVisual) {
		for (int i = 0; i < nbVisuals; ++i) {
			// Get the current visual attributes
			int RGBA, doubleBuffer, red, green, blue, alpha, depth, stencil,
			        multiSampling, samples;
			glXGetConfig(window->getDisplay(), &visuals[i], GLX_RGBA, &RGBA);
			glXGetConfig(window->getDisplay(), &visuals[i], GLX_DOUBLEBUFFER,
			        &doubleBuffer);
			glXGetConfig(window->getDisplay(), &visuals[i], GLX_RED_SIZE, &red);
			glXGetConfig(window->getDisplay(), &visuals[i], GLX_GREEN_SIZE,
			        &green);
			glXGetConfig(window->getDisplay(), &visuals[i], GLX_BLUE_SIZE,
			        &blue);
			glXGetConfig(window->getDisplay(), &visuals[i], GLX_ALPHA_SIZE,
			        &alpha);
			glXGetConfig(window->getDisplay(), &visuals[i], GLX_DEPTH_SIZE,
			        &depth);
			glXGetConfig(window->getDisplay(), &visuals[i], GLX_STENCIL_SIZE,
			        &stencil);
			glXGetConfig(window->getDisplay(), &visuals[i],
			        GLX_SAMPLE_BUFFERS_ARB, &multiSampling);
			glXGetConfig(window->getDisplay(), &visuals[i], GLX_SAMPLES_ARB,
			        &samples);

			// First check the mandatory parameters
			if ((RGBA == 0) || (doubleBuffer == 0)) {
				continue;
			}

			// Evaluate the current configuration
			int color = red + green + blue + alpha;
			int score = evaluateFormat(bitsPerPixel, this->configuration,
			        color, depth, stencil, multiSampling ? samples : 0); // < FIXTHIS

			// Keep it if it's better than the current best
			if (score < bestScore) {
				bestScore = score;
				bestVisual = &visuals[i];
			}
		}

		// If no visual has been found, try a lower level of antialiasing
		if (!bestVisual) {
			if (this->configuration.getAntialiasingLevel() > 2) {
				std::cerr << "Failed to find a pixel format supporting "
				        << this->configuration.getAntialiasingLevel()
				        << " antialiasing levels ; trying with 2 levels"
				        << std::endl;
				this->configuration.setAntialiasingLevel(2);
			} else if (this->configuration.getAntialiasingLevel() > 0) {
				std::cerr
				        << "Failed to find a pixel format supporting antialiasing ; antialiasing will be disabled"
				        << std::endl;
				this->configuration.setAntialiasingLevel(0);
			} else {
				std::cerr
				        << "Failed to find a suitable pixel format for the window -- cannot create OpenGL context"
				        << std::endl;
				return;
			}
		}
	}

	return bestVisual;
}
#endif
GLXFBConfig* X11Context::getFramebufferConfig( int& fbCount ) {
	static int visual_attribs[] = {
			GLX_RENDER_TYPE, GLX_RGBA_BIT,
	        GLX_DRAWABLE_TYPE, GLX_WINDOW_BIT,
	        GLX_DOUBLEBUFFER, true,
	        GLX_RED_SIZE, 1,
	        GLX_GREEN_SIZE, 1,
	        GLX_BLUE_SIZE, 1,
	        None
	};

	LOG_DEBUG << "[X11Context] Getting framebuffer config";
	GLXFBConfig *fbc = glXChooseFBConfig( display, DefaultScreen( display ), visual_attribs, &fbCount );
	return fbc;
}

XVisualInfo* X11Context::getVisualFromFBConfig( GLXFBConfig fbConfig ) {
	XVisualInfo *vi = glXGetVisualFromFBConfig(display, fbConfig);
	return vi;
}

GLXContext X11Context::initializeOldStyleContext() {
#if 0
	XWindowAttributes windowAttributes = getXWindowAttributes();

	XVisualInfo templ = createVisualInfoTemplate( windowAttributes );

	int nbVisuals = 0;
	XVisualInfo* visuals = getPossibleVisuals( nbVisuals, templ );

	XVisualInfo* bestVisual = findBestVisual( nbVisuals, visuals );
#endif
	int fbConfigCount;
	GLXFBConfig* fbConfigs = getFramebufferConfig( fbConfigCount );

	// TODO choose one of the fbConfigs instead of using the first one
	XVisualInfo* vi = getVisualFromFBConfig( fbConfigs[0] );

	// Get the context to share display lists with
	GLXContext toShare = NULL; //shared ? shared->myContext : NULL; // < FIXTHIS

	// Create an oldstyle context first, to get the correct function pointer for glXCreateContextAttribsARB
	GLXContext oldContext = glXCreateContext( display, vi, 0, GL_TRUE );
	return oldContext;
}

void X11Context::initializeImpl() {

	// first of all determin glx version
	int glxmajor, glxminor;
	glXQueryVersion( display, &glxmajor, &glxminor);
	LOG_DEBUG << "[X11Context] glx version: " << glxmajor << "." << glxminor;

	GLXContext oldContext = initializeOldStyleContext();

	GLXDrawable curr;
	if ( window ) {
		curr = window->getWindow();
	}
	else {
		int attributes[] =
		{
		  GLX_RGBA,
		  GLX_RED_SIZE, 8,
		  GLX_GREEN_SIZE, 8,
		  GLX_BLUE_SIZE, 8,
		  GLX_DEPTH_SIZE, 16,
		  0,        /* Save space for GLX_DOUBLEBUFFER */
		  0
		};
		XVisualInfo *vinfo = glXChooseVisual(display, DefaultScreen(display), attributes);
		Pixmap pixmap;
		GLXPixmap glxpixmap;
		pixmap    = XCreatePixmap(display, DefaultRootWindow(display), 20, 20, vinfo->depth);
		glxpixmap = glXCreateGLXPixmap(display, vinfo, pixmap);

		curr = glxpixmap;
	}



	glXMakeCurrent( display, curr, oldContext );

	// try to get a handle to the new context creation method
	glXCreateContextAttribsARBProc glXCreateContextAttribsARB = NULL;
	glXCreateContextAttribsARB = (glXCreateContextAttribsARBProc) glXGetProcAddress( (const GLubyte*) "glXCreateContextAttribsARB" );
	if (glXCreateContextAttribsARB == NULL) {
		// OpenGL 2.1
		LOG_INFO << "[X11Context] could not get new style context creation method - Only OpenGL 2.1 will be available";
		context = oldContext;
	}
	else {
		// OpenGL 3.0+

		glXMakeCurrent( display, 0, 0 );
		glXDestroyContext( display, oldContext );
		static int context_attribs[] = {
				GLX_CONTEXT_MAJOR_VERSION_ARB, configuration.getMajorVersion(),
				GLX_CONTEXT_MINOR_VERSION_ARB, configuration.getMinorVersion(),
				GLX_CONTEXT_FLAGS_ARB,
				GLX_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB,
				None
		};

		LOG_DEBUG << "[X11Context] creating an OpenGL " << configuration.getMajorVersion() << "." << configuration.getMinorVersion() << " context";
		GLXContext toShare = 0;
		if ( shareContext != 0 ) {
			toShare = dynamic_cast<X11Context*>(shareContext)->context;
		}
		int fbCount;
		GLXFBConfig* fbConfigs = getFramebufferConfig( fbCount );
		context = glXCreateContextAttribsARB(display, fbConfigs[0], toShare, GL_TRUE, context_attribs);

	}

	if (!context) {
		LOG_ERROR << "[X11Context] Failed to create an OpenGL Context!";
		throw "Failed to create an OpenGL context for this window";
	}

	if (glXIsDirect(display, context)) {
		LOG_DEBUG << "[X11Context] direct rendering enabled";
	} else {
		LOG_WARNING << "[X11Context] direct rendering not available!";
	}

	// Update the creation settings from the chosen format
	int depth, stencil;
/*	glXGetConfig(this->window->getDisplay(), bestVisual, GLX_DEPTH_SIZE, &depth);
	glXGetConfig(this->window->getDisplay(), bestVisual, GLX_STENCIL_SIZE, &stencil);
	this->configuration.setDepthBits(static_cast<unsigned int> (depth));
	this->configuration.setStencilBits(static_cast<unsigned int> (stencil));

	// Change the target window's colormap so that it matches the context's one
	::Window root = RootWindow(this->window->getDisplay(), DefaultScreen(
	        this->window->getDisplay()));
	Colormap colMap = XCreateColormap(this->window->getDisplay(), root, bestVisual->visual, AllocNone);
	XSetWindowColormap(this->window->getDisplay(), this->window->getWindow(),
	        colMap);

	// Free the temporary visuals array
	XFree(visuals);
*/

	glXMakeCurrent( display, curr, context);

	GLint minor, major;
	glGetIntegerv( GL_MAJOR_VERSION, &major );
	glGetIntegerv( GL_MINOR_VERSION, &minor );
	LOG_INFO << "[X11Context] OpenGL Vendor: " << glGetString(GL_VENDOR);
	LOG_INFO << "[X11Context] OpenGL Renderer: " << glGetString(GL_RENDERER);
	LOG_INFO << "[X11Context] OpenGL Version: " << glGetString(GL_VERSION) << " (" << major << "." << minor << ")";
	LOG_INFO << "[X11Context] OpenGL Shading Language Version: " << glGetString(
	        GL_SHADING_LANGUAGE_VERSION);

	//LOG_INFO << "OpenGL Extensions: " << glGetString(GL_EXTENSIONS);

	glXMakeCurrent( display, None, NULL );

}

void X11Context::makeCurrent() {
	LOG_DEBUG4 << "[X11Context] makeCurrent()";

	glXMakeCurrent( display, window->getWindow(), context );

}

void X11Context::swapBuffers() {

	glXSwapBuffers( display, window->getWindow());

}

void X11Context::dispose() {

	glXDestroyContext( display, context);

}

} // namespace priv
} // namespace glwak
